﻿using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace Ultima.Xna.Data
{
    public class TileMatrix
    {
        private static int[] _mapBlockHeightList = new int[] { 512, 512, 200, 256, 181 };
        private static StaticTileList[][] _staticTileLists;
        private static StaticTile[] _staticTileBuffer = new StaticTile[128];

        private StaticTile[][][] _emptyStaticsBlock;
        private Tile[] _invalidLandBlock;
        private int[][] _landPatches;
        private Tile[][][] _landTiles;
        private int[][] _staticsPatches;
        private StaticTile[][][][][] _staticTiles;

        private int _blockHeight;
        private int _blockWidth;
        private int _height;

        private BinaryReader _indexReader;
        private FileStream _indexStream;
        private FileStream _mapStream;
        private TileMatrixPatch _patch;
        private FileStream _statics;
        private int _width;
        private List<TileMatrix> _fileShare = new List<TileMatrix>();

        public int BlockHeight
        {
            get { return _blockHeight; }
        }

        public int BlockWidth
        {
            get { return _blockWidth; }
        }

        public StaticTile[][][] EmptyStaticsBlock
        {
            get { return _emptyStaticsBlock; }
        }

        public int Height
        {
            get { return _height; }
        }

        public BinaryReader IndexReader
        {
            get { return _indexReader; }
            set { _indexReader = value; }
        }

        public FileStream IndexStream
        {
            get { return _indexStream; }
            set { _indexStream = value; }
        }

        public FileStream MapStream
        {
            get { return _mapStream; }
            set { _mapStream = value; }
        }

        public TileMatrixPatch Patch
        {
            get { return _patch; }
        }

        public FileStream Statics
        {
            get { return _statics; }
            set { _statics = value; }
        }

        public int Width
        {
            get { return _width; }
        }

        public TileMatrix(int index, int id)
        {
            _mapStream = FileManager.GetFile("map{0}.mul", index);
            _indexStream = FileManager.GetFile("staidx{0}.mul", index);
            _indexReader = new BinaryReader(_indexStream);
            _statics = FileManager.GetFile("statics{0}.mul", index);

            _height = _mapBlockHeightList[index] << 3;
            _blockHeight = _mapBlockHeightList[index];
            _width = (int)_mapStream.Length / (_blockHeight * 196) << 3;
            _blockWidth = _width >> 3;

            _emptyStaticsBlock = new StaticTile[8][][];

            for (int i = 0; i < 8; ++i)
            {
                _emptyStaticsBlock[i] = new StaticTile[8][];

                for (int j = 0; j < 8; ++j)
                {
                    _emptyStaticsBlock[i][j] = new StaticTile[0];
                }
            }

            _invalidLandBlock = new Tile[196];
            _landPatches = new int[_blockWidth][];
            _landTiles = new Tile[_blockWidth][][];
            _staticTiles = new StaticTile[_blockWidth][][][][];
            _staticsPatches = new int[_blockWidth][];

            _patch = new TileMatrixPatch(this, id);
        }

        public void SetStaticBlock(int x, int y, StaticTile[][][] value)
        {
            if (x < 0 || y < 0 || x >= _blockWidth || y >= _blockHeight)
            {
                return;
            }

            if (_staticTiles[x] == null)
            {
                _staticTiles[x] = new StaticTile[_blockHeight][][][];
            }

            _staticTiles[x][y] = value;

            if (_staticsPatches[x] == null)
            {
                _staticsPatches[x] = new int[(_blockHeight + 31) >> 5];
            }

            _staticsPatches[x][y >> 5] |= 1 << (y & 0x1F);
        }

        public StaticTile[][][] GetStaticBlock(int x, int y)
        {
            if (x < 0 || y < 0 || x >= _blockWidth || y >= _blockHeight || _statics == null || _indexStream == null)
            {
                return _emptyStaticsBlock;
            }

            if (_staticTiles[x] == null)
            {
                _staticTiles[x] = new StaticTile[_blockHeight][][][];
            }

            if (_staticTiles[x][y] == null)
            {
                _staticTiles[x][y] = ReadStaticBlock(x, y);
            }

            return _staticTiles[x][y];
        }

        public StaticTile[] GetStaticTiles(int x, int y)
        {
            StaticTile[][][] tiles = GetStaticBlock(x >> 3, y >> 3);

            return tiles[x & 0x07][y & 0x07];
        }

        public void SetLandBlock(int x, int y, Tile[] value)
        {
            if (x < 0 || y < 0 || x >= _blockWidth || y >= _blockHeight)
            {
                return;
            }

            if (_landTiles[x] == null)
            {
                _landTiles[x] = new Tile[_blockHeight][];
            }

            _landTiles[x][y] = value;

            if (_landPatches[x] == null)
            {
                _landPatches[x] = new int[(_blockHeight + 31) >> 5];
            }

            _landPatches[x][y >> 5] |= 1 << (y & 0x1F);
        }

        public Tile[] GetLandBlock(int x, int y)
        {
            if (x < 0 || y < 0 || x >= _blockWidth || y >= _blockHeight || _mapStream == null)
            {
                return _invalidLandBlock;
            }

            if (_landTiles[x] == null)
            {
                _landTiles[x] = new Tile[_blockHeight][];
            }

            if (_landTiles[x][y] == null)
            {
                _landTiles[x][y] = ReadLandBlock(x, y);
            }

            return _landTiles[x][y];
        }

        public Tile GetLandTile(int x, int y)
        {
            Tile[] tiles = GetLandBlock(x >> 3, y >> 3);

            return tiles[((y & 0x07) << 3) + (x & 0x07)];
        }

        private unsafe StaticTile[][][] ReadStaticBlock(int x, int y)
        {
            try
            {
                _indexReader.BaseStream.Seek(((x * _blockHeight) + y) * 12, SeekOrigin.Begin);

                int lookup = _indexReader.ReadInt32();
                int length = _indexReader.ReadInt32();

                if (lookup < 0 || length <= 0)
                {
                    return _emptyStaticsBlock;
                }
                else
                {
                    int count = length / 7;

                    _statics.Seek(lookup, SeekOrigin.Begin);

                    if (_staticTileBuffer.Length < count)
                    {
                        _staticTileBuffer = new StaticTile[count];
                    }

                    StaticTile[] staticTiles = _staticTileBuffer;

                    fixed (StaticTile* pStaticTiles = staticTiles)
                    {
                        NativeMethods._lread(_statics.SafeFileHandle.DangerousGetHandle(), pStaticTiles, length);

                        if (_staticTileLists == null)
                        {
                            _staticTileLists = new StaticTileList[8][];

                            for (int i = 0; i < 8; ++i)
                            {
                                _staticTileLists[i] = new StaticTileList[8];

                                for (int j = 0; j < 8; ++j)
                                {
                                    _staticTileLists[i][j] = new StaticTileList();
                                }
                            }
                        }

                        StaticTileList[][] staticTileLists = _staticTileLists;

                        StaticTile* pCurrent = pStaticTiles;
                        StaticTile* pEnd = pStaticTiles + count;

                        while (pCurrent < pEnd)
                        {
                            staticTileLists[pCurrent->X & 0x07][pCurrent->Y & 0x07].Add((short)((pCurrent->ID & 0x3FFF) + 0x4000), pCurrent->Z);

                            pCurrent++;
                        }

                        StaticTile[][][] tiles = new StaticTile[8][][];

                        for (int i = 0; i < 8; ++i)
                        {
                            tiles[i] = new StaticTile[8][];

                            for (int j = 0; j < 8; ++j)
                            {
                                tiles[i][j] = staticTileLists[i][j].ToArray();
                            }
                        }

                        return tiles;
                    }
                }
            }
            catch (EndOfStreamException)
            {
                return _emptyStaticsBlock;
            }
        }

        private unsafe Tile[] ReadLandBlock(int x, int y)
        {
            try
            {
                _mapStream.Seek(((x * _blockHeight) + y) * 196 + 4, SeekOrigin.Begin);

                Tile[] tiles = new Tile[64];

                fixed (Tile* pTiles = tiles)
                {
                    NativeMethods._lread(_mapStream.SafeFileHandle.DangerousGetHandle(), pTiles, 192);
                }

                return tiles;
            }
            catch
            {
                return _invalidLandBlock;
            }
        }

        public void Dispose()
        {
            if (_indexReader != null)
            {
                _indexReader.Close();
            }

            if (_mapStream != null)
            {
                _mapStream.Close();
            }

            if (_statics != null)
            {
                _statics.Close();
            }
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct StaticTile
    {
        public short ID;
        public byte X;
        public byte Y;
        public sbyte Z;
        public short Hue;

        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();

            stringBuilder.AppendLine("ID: " + ID.ToString());
            stringBuilder.AppendLine("X: " + X.ToString());
            stringBuilder.AppendLine("Y: " + Y.ToString());
            stringBuilder.AppendLine("Z: " + Z.ToString());
            stringBuilder.AppendLine("Hue: " + Hue.ToString());

            return stringBuilder.ToString();
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct Tile
    {
        public short ID;
        public sbyte Z;

        public Tile(short id, sbyte z)
        {
            ID = id;
            Z = z;
        }

        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();

            stringBuilder.AppendLine("ID: " + ID.ToString());
            stringBuilder.AppendLine("Z: " + Z.ToString());

            return stringBuilder.ToString();
        }
    }
}





















/*using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;

namespace UltimaXNA
{
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct LandTile
    {
        public short ID;
        public sbyte Z;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct StaticTile
    {
        public short ID;
        public byte X;
        public byte Y;
        public sbyte Z;
        public short Hue;
    }

    class TileMatrix
    {
        private static int[] _MapBlockHeightList = new int[] {*/
            //512 /* Felucca */,
            //512 /* Trammel */,
            //200 /* Ilshenar */,
            //256 /* Malas */, 
            //181 /* Tokuno */
        /*};

        private int _Height;
        private FileStream _Map;
        private int _BlockHeight;
        private int _BlockWidth;
        private LandTile[][][] _LandTiles;
        private FileStream _Statics;
        private FileStream _StaticsIndex;
        private int _Width;

        public TileMatrix(int index)
        {
            _Map = FileManager.GetFile("map{0}.mul", index);

            _Statics = FileManager.GetFile("statics{0}.mul", index);

            _StaticsIndex = FileManager.GetFile("staidx{0}.mul", index);

            _Height = _MapBlockHeightList[index] << 3;

            _BlockHeight = _MapBlockHeightList[index];

            _Width = (int)_Map.Length / (_BlockHeight * 196) << 3;

            _BlockWidth = _Width >> 3;

            _LandTiles = new LandTile[_BlockWidth][][];
        }

        private LandTile[] GetLandBlock(int x, int y)
        {
            if (_LandTiles[x] == null)
            {
                _LandTiles[x] = new LandTile[_BlockHeight][];
            }

            LandTile[] landTiles = _LandTiles[x][y];

            if (landTiles == null)
            {
                _LandTiles[x][y] = landTiles = ReadLandBlock(x, y);
            }

            return landTiles;
        }

        public LandTile GetLandTile(int x, int y)
        {
            LandTile[] landTile = GetLandBlock(x >> 3, y >> 3);

            return landTile[((y & 0x07) << 3) + (x & 0x07)];
        }

        private unsafe LandTile[] ReadLandBlock(int x, int y)
        {
            _Map.Seek(((x * _BlockHeight) + y) * 196 + 4, SeekOrigin.Begin);

            LandTile[] landTiles = new LandTile[64];

            fixed (LandTile* pLandTiles = landTiles)
            {
                NativeMethods.Read(_Map.SafeFileHandle.DangerousGetHandle(), pLandTiles, 192);
            }

            return landTiles;
        }
    }
}*/